#!/usr/bin/python3
# -*- coding:utf-8 -*-
""""
@Author: 李吉凯
@Email: lijk34925@hundsun.com
@Time: 2022/5/9 13:48
@File: front_end.py
"""
import datetime
import os

from qturboframe.lib.utils.by import By
from qturboframe.lib.webdriver.exceptions import SeleniumError
from qturboframe.lib.utils.check import CheckBasic

from .hui_lib.base_table import BaseTable
from .hui_lib.check import CheckSingle
from .hui_lib.index_table import Index_Table
from .hui_lib.table_new import NewTable
from ..hui_app.front_end import FrontEnd as HuiFrontEnd
from ..Lib.Common import logFilePath, logger
from .hui_lib.menu import Menu
from .hui_lib.form import Form
from .hui_lib.page import Page
from .hui_lib.list import List
from .hui_lib.tree import Tree
from .hui_lib.input_type import InputType, InputTypeNoStandard
from .hui_lib.home_tab import HomeTab
from .hui_lib.table import Table
from .hui_lib.select import Select
from .hui_lib.msg_box import MsgBox
from .hui_lib.edit_grid import EditGrid
from .hui_lib.tree_grid import TreeGrid
from .hui_lib.el_tree import ElTree
from .hui_lib.select_tree import SelectTree
from .hui_lib.link_api import LinkApi
from .hui_lib.mail_textarea import MailTextarea
from .hui_lib.transfer_table import TransferTable
from .hui_lib.calendar import Calendar
from .hui_lib.button import Button
from .hui_lib.u_table import U_Table
from .hui_lib.select_table import SelectTable

class FrontEnd(HuiFrontEnd, CheckBasic):
    '''
    功能说明：
        class for data_risk_ui
        数据风险部的前端UI测试框架的入口类
        hui框架组件总览： http://hui.hundsun.com/component/
        web自动化框架： 实现对hui类型组件的测试支持，思路：每个组件比对应1个工具类，提供查找、操作、检查的方法。写脚本时直接调用即可。
    使用说明：
        1.在__init__创建对象
        2.调用已经封装好的组件，即../hui_lib 下的组件(继承重新的组件)；若组件未重新封装，可不必引入，但还是可以调用父类HuiFrontEnd中的组件
        3.元素查找输入的格式：
            3-1.含入参是 text 的组件对象，可以直接给定页面上的标签的文本值。即可定位，例如： "登录";
            3-2.idname参数的传参方法，一般包括一下2类：
                (1).定位页面组件元素的 id ，填写。例如： idname='mainSearch'
                (2).组件定位条件，输入形式是 字典类型。标准：{'元素查找方式':'元素查找语句', '辅助属性':'辅助属性值'}
                    元素查找方式: 支持selenium的8大类(引入By类可见)元素定位方法查找，常用的xpath和css selector：
                        {By.XPATH: './/span[text()="退出登录"]//ancestor::li[1]'}
                        {By.CSS_SELECTOR: 'div.menus-container'}，
                    辅助属性: 根据目标组件标签的属性名称填写。
                        {By.CSS_SELECTOR: 'div.h-system-item>ul>div>li', 'text': '工作台'}
        4.找到元素，即可调用对应组件的操作方法。
    '''

    def __init__(self, driver):
        """
        初始化
        """
        self.driver = driver
        self.timeout = 10

    def get_screen(self):
        # 截图函数
        try:
            cur_time = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
            png_name = cur_time + '.png'
            pngFile = os.path.join(logFilePath, png_name)
            self.driver.get_screenshot_as_file(pngFile)
            logger.info("Web自动化测试截图路径：" + pngFile)
        except BaseException as e:
            logger.error("Failed to take Screenshots! %s" % e)

    def menu(self, idname=None):
        '''
        左侧菜单栏对象

        :Args:
         - idname: 菜单栏的定位条件，字典类型：{'元素查找方式':'元素查找语句', '辅助属性':'辅助属性值'}

        :Raises:
            SeleniumError: 菜单操作失败

        :Return:
            返回Menu对象
        '''
        if idname:
            menu = Menu(idname, self.driver, self.timeout)
        else:
            menu = Menu({'class name': 'h-sidebar-menu'}, self.driver, self.timeout)
        return menu

    def form(self, text=None, idname=None, father=None, index=1):
        '''
        获取一般表单对象，技术部封装的是div弹框中的hui表单对象，有部分差异所以在这里重写一个

        :Args:
         - idname: 表单的定位条件，字典类型：{'元素查找方式':'元素查找语句', '辅助属性':'辅助属性值'}
         - father: 表单的祖先级元素，可以给表单查找限定范围，这样就不需要在整个页面中查找

        :Returns:
            hui的表单对象
        '''
        if father is None:
            father = self.driver
        if text:
            return Form({By.TEXT: text}, father, self.timeout, index=index)
        if idname:
            form = Form(idname, father, self.timeout)
        else:
            # 针对整个father下面有多个h-form加上这个方法来暂时规避处理(引起的原因主要还是因为前端代码不规范)
            if father.exist('css selector', 'form.h-form.h-form-row', timeout=0.1):
                form = Form({'css selector': 'form.h-form.h-form-row'}, father, self.timeout)
            else:
                form = Form({'css selector': 'form.h-form'}, father, self.timeout)
        return form

    def tree(self, idname=None, father=None):
        '''
        获取树形控件对象

        :Args:
         - idname: 树形控件的定位条件，字典类型：{'元素查找方式':'元素查找语句', '辅助属性':'辅助属性值'}
         - father: 树形控件的祖先级元素，可以给表单查找限定范围，这样就不需要在整个页面中查找

        :Returns:
            hui的树形控件对象
        '''
        if father is None:
            father = self.driver
        if idname:
            tree = Tree(idname, father, self.timeout)
        else:
            tree = Tree({'css selector': 'ul.h-tree'}, father, self.timeout)
        return tree

    def input_type(self, idname=None, father=None, model='standard'):
        '''
        获取hui文本框对象，适用于密码框

        :Args:
         - idname: 文本框的定位条件，字典类型：{'元素查找方式':'元素查找语句', '辅助属性':'辅助属性值'}
         - father: 文本框的祖先级元素，可以给文本框查找限定范围，这样就不需要在整个页面中查找

        :Returns:
            hui的文本框对象
        '''
        if father is None:
            father = self.driver
        if idname:
            if model == 'standard':
                input_type = InputType(idname, father, self.timeout)
            else:
                input_type = InputTypeNoStandard(idname, father, self.timeout)
        else:
            input_type = InputType(
                {'css selector': 'input.h-input'}, father, self.timeout)
        return input_type

    def home_tab(self, tab_name):
        '''
        获取首页选项卡对象

        :Args:
         - tab_name: 选项卡名称

        :Returns:
            首页选项卡对象
        '''
        return HomeTab(tab_name, self.driver)

    def table_base(self, idname=None, father=None):
        '''
        面向最简单的表格操作对象 <table><header></header><body></body></table>
        :param idname:
        :param father:
        :param index:
        :return:
        '''
        if father is None:
            father = self.driver
        if idname:
            table = BaseTable(idname, father, self.timeout)
        else:
            # table = Table({'css selector': 'div.h-table-wrapper'}, father, self.timeout)
            # 直接找最大的那个模块，包括了 表头按钮栏 h-datagrid-toolbar，表格 h-table-wrapper 以及 表格分页栏 h-table-pageView
            table = BaseTable({'xpath': './/div[@class="h-table-body"]/table'}, father, self.timeout)
        return table

    def table(self, idname=None, father=None, index=1):
        '''
        获取表格对象
        注意：继承关系，子类和父类的参数需相同
        :Args:
         - idname: 表格的定位条件，字典类型：{'元素查找方式':'元素查找语句', '辅助属性':'辅助属性值'}
         - father: 表格的祖先级元素，可以给表格查找限定范围，这样就不需要在整个页面中查找

        :Returns:
            返回表格对象
        '''
        if father is None:
            father = self.driver
        if idname:
            table = Table(idname, father, self.timeout, index=index)
        else:
            table = Table({'css selector': 'div.h-table-wrapper'}, father, self.timeout)
        return table

    def table_new(self, idname=None, father=None, index=1):
        '''
        获取表格对象
        注意：继承关系，子类和父类的参数需相同
        :Args:
         - idname: 表格的定位条件，字典类型：{'元素查找方式':'元素查找语句', '辅助属性':'辅助属性值'}
         - father: 表格的祖先级元素，可以给表格查找限定范围，这样就不需要在整个页面中查找

        :Returns:
            返回表格对象
        '''
        if father is None:
            father = self.driver

        if idname:
            table = NewTable(idname, father, self.timeout, index=index)
        else:
            # table = Table({'css selector': 'div.h-table-wrapper'}, father, self.timeout)
            # 直接找最大的那个模块，包括了 表头按钮栏 h-datagrid-toolbar，表格 h-table-wrapper 以及 表格分页栏 h-table-pageView
            table = NewTable({'xpath': './/div[@class="h-table-wrapper"]//table'}, father, self.timeout)
        return table

    def indextable(self, idname=None, father=None, index=1):
        '''
        获取表格对象
        注意：继承关系，子类和父类的参数需相同
        :Args:
         - idname: 表格的定位条件，字典类型：{'元素查找方式':'元素查找语句', '辅助属性':'辅助属性值'}
         - father: 表格的祖先级元素，可以给表格查找限定范围，这样就不需要在整个页面中查找

        :Returns:
            返回表格对象
        '''
        if father is None:
            father = self.driver
        if idname:
            table = Index_Table(idname, father, self.timeout, index=index)
        else:
            table = Index_Table({'css selector': 'div.h-table-wrapper'}, father, self.timeout, index=index)
        return table

    def u_table(self,idname=None, father=None, index=1):
        '''
        资讯云产品-运行管理 判定专写的组件 其他类似情况需要注意兼容情况
        :Args:
         - idname: 表格的定位条件，字典类型：{'元素查找方式':'元素查找语句', '辅助属性':'辅助属性值'}
         - father: 表格的祖先级元素，可以给表格查找限定范围，这样就不需要在整个页面中查找
         - index: 页面中的第几张表，在该组间中可以用来挑选那张表进行操作（在table组间中该字段使用只有用text查找时启用）
        :Returns:
            返回表格对象
        '''
        if father is None:
            father = self.driver
        if idname:
            table = U_Table(idname, father, self.timeout, index=index)
        else:
            table = U_Table({'css selector': 'div.u-table-wrapper'}, father, self.timeout,index=index)
        return table

    def page(self, idname=None, father=None,  timeout=3):
        '''
        获取表格分页栏对象

        :Args:
         - idname: 表格分页栏的定位条件，字典类型：{'元素查找方式':'元素查找语句', '辅助属性':'辅助属性值'}
         - father: 表格分页栏的祖先级元素，可以给表格分页查找限定范围，这样就不需要在整个页面中查找

        :Returns:
            返回表格分页栏对象

        :Example:
         - 例1: page = FrontEnd(driver).page()
         - 例2: page = FrontEnd(driver).page({By.XPATH:'./parent::div/ul'}, father=table.element)
        '''
        if father is None:
            father = self.driver
        if idname:
            page = Page(idname, father, timeout)
        else:
            page = Page({'css selector': 'ul.h-page'}, father,timeout)
        return page

    def check_message(self, ui_expect, idname=None):
        '''
        进行弹出提示框校验

        :Args:
         - ui_expect: 期望
         - idname: 表格的定位条件，字典类型：{'元素查找方式':'元素查找语句', '辅助属性':'辅助属性值'}

        :Returns:

        '''
        if ui_expect:
            message = self.message(idname)
            if message.exist():
                message.check(ui_expect)
                close_btn = message.element.exist('css selector', 'a.h-message-notice-close', timeout=0.1)
                if close_btn:
                    message.close()
            else:
                raise SeleniumError('未找到消息框！')

    def list(self, idname=None, father=None):
        if father is None:
            father = self.driver
        if idname:
            list_web = List(idname, father, self.timeout)
            return list_web
        else:
            raise SeleniumError(f'定位表达式为空！')

    def check_single(self, idname=None, father=None):
        if father is None:
            father = self.driver
        if idname:
            check = CheckSingle(idname, father, self.timeout)
        else:
            check = CheckSingle({'xpath': './*[contains(@class,"h-checkbox-wrapper")]'},
                                father, self.timeout)
        return check

    def select(self, idname=None, father=None):
        '''
        获取select下拉框对象

        :Args:
         - idname: 下拉框的定位条件，字典类型：{'元素查找方式':'元素查找语句', '辅助属性':'辅助属性值'}
         - father: 下拉框的祖先级元素，可以给下拉框查找限定范围，这样就不需要在整个页面中查找

        :Returns:
            hui的下拉框对象

        :Example:
         - 例1: select = FrontEnd(driver).select(father=form)
         - 例2: select = FrontEnd(driver).select({By.ID:'id'})
        '''
        if father is None:
            father = self.driver
        if idname:
            select = Select(idname, father, self.timeout)
        else:
            select = Select(
                {'css selector': 'input.h-select-input'}, father, self.timeout)
        return select

    def msg_box(self, text=None, idname=None, father=None, timeout=0.1):
        '''
        获取hui的弹出框对象。
        因为弹框框比较多，类型也不一致。定位比较复杂。无法给默认值。但是提供一种相对简单的方法，text值给出弹框中心文本内容的关键字样进行查找。
        :Args:
         - text: 弹出框中心文本的内容，包含关键字段即可。
                例如： {'header': '弹框标题栏关键字'}
                   或 {'body': '弹框中间的文本中的关键字'}
                           或  '弹框中间的文本中的关键字'   ----默认从文本框中取
         - idname: 弹出框的定位条件，字典类型：{'元素查找方式':'元素查找语句', '辅助属性':'辅助属性值'}

        :Returns:
            弹出框对象

        :Example:
         - 例1: popwindow = FrontEnd(driver).msg_box()
         - 例2: popwindow = FrontEnd(driver).msg_box({By.ID:'id'})
        '''
        if father is None:
            father = self.driver
        msgbox = MsgBox(text=text, idname=idname, father=father, timeout=timeout)
        return msgbox

    def editgrid_table(self, idname=None, father=None):
        '''
        获取editgrid表格对象

        :Args:
         - idname: editgrid表格的定位条件，字典类型：{'元素查找方式':'元素查找语句', '辅助属性':'辅助属性值'}
         - father: editgrid表格的祖先级元素，可以给editgrid表格查找限定范围，这样就不需要在整个页面中查找

        :Returns:
            返回editgrid表格对象

        :Example:
         - 例1: editgrid = FrontEnd(driver).editgrid_table(father=msg)
         - 例2: editgrid = FrontEnd(driver).editgrid_table({By.ID:'id'})
        '''
        if father is None:
            father = self.driver
        if idname:
            table = EditGrid(idname, father, self.timeout)
        else:
            table = EditGrid({'css selector': 'div.h-editgird-wrapper'}, father, self.timeout)
        return table

    def tree_table(self, idname=None, father=None):
        '''
        获取树形表格固定栏对象(特,有勾选在fix固定栏中)

        :Args:
         - idname: 树形表格的定位条件，字典类型：{'元素查找方式':'元素查找语句', '辅助属性':'辅助属性值'}
         - father: 树形表格的祖先级元素，可以给树形表格查找限定范围，这样就不需要在整个页面中查找

        :Returns:
            hui的树形表格对象

        :Example:
         - 例1: tree_table = FrontEnd(driver).tree_table(father=msg_box)
         - 例2: tree_table = FrontEnd(driver).tree_table({By.ID:'id'})
        '''
        if father is None:
            father = self.driver
        if idname:
            tree_table = TreeGrid(idname, father, self.timeout)
        else:
            tree_table = TreeGrid({'css selector': 'div.h-editgird-wrapper'}, father, self.timeout)
        return tree_table

    def el_tree(self, idname=None, father=None):
        '''
        获取可拖动树形控件对象

        :Args:
         - idname: 树形控件的定位条件，字典类型：{'元素查找方式':'元素查找语句', '辅助属性':'辅助属性值'}
         - father: 树形控件的祖先级元素，可以给表单查找限定范围，这样就不需要在整个页面中查找

        :Returns:
            hui的树形控件对象
        '''
        if father is None:
            father = self.driver
        if idname:
            tree = ElTree(idname, father, self.timeout)
        else:
            tree = ElTree({'css selector': 'div.el-tree'}, father, self.timeout)
        return tree

    def select_tree(self, idname=None, father=None):
        '''
        下拉树形框对象

        :Args:
         - idname: 下拉树形控件的定位条件，字典类型：{'元素查找方式':'元素查找语句', '辅助属性':'辅助属性值'}
         - father: 下拉树形框的祖先级元素，可以给下拉树形框查找限定范围，这样就不需要在整个页面中查找

        :Returns:
            hui的下拉树形框对象

        :Example:
         - 例1: select_tree = FrontEnd(driver).select_tree(father=form)
         - 例2: select_tree = FrontEnd(driver).select_tree({By.ID:'id'})
        '''
        if father is None:
            father = self.driver
        if idname:
            select_tree = SelectTree(idname, father, self.timeout)
        else:
            select_tree = SelectTree(
                {'css selector': 'input.h-selectTree-input'}, father, self.timeout)
        return select_tree

    def link_api(self, idname=None, father=None):
        '''
        获取功能权限菜单关联API弹出框多个表单编辑对象

        :Args:
         - idname: 树形控件的定位条件，字典类型：{'元素查找方式':'元素查找语句', '辅助属性':'辅助属性值'}
         - father: 树形控件的祖先级元素，可以给表单查找限定范围，这样就不需要在整个页面中查找

        :Returns:
            功能权限菜单关联API弹出框多个表单编辑对象
        '''
        if father is None:
            father = self.driver
        if idname:
            panel = LinkApi(idname, father, self.timeout)
        else:
            panel = LinkApi({'css selector': 'div.h-panel'}, father, self.timeout)
        return panel

    def mail_textarea(self, idname=None, father=None):
        '''
        特有邮件样式文本框对象

        :Args:
         - idname: 邮件文本框控件的定位条件，字典类型：{'元素查找方式':'元素查找语句', '辅助属性':'辅助属性值'}
         - father: 邮件文本框控件的祖先级元素，可以给表单查找限定范围，这样就不需要在整个页面中查找

        :Returns:
            特有邮件样式文本框对象
        '''
        if father is None:
            father = self.driver
        if idname:
            mail_textarea = MailTextarea(idname, father, self.timeout)
        else:
            mail_textarea = MailTextarea({'css selector': 'div.bizframe-hs-editor_2RqIvYze'}, father, self.timeout)
        return mail_textarea

    def transfer_table(self, idname=None, father=None):
        '''
        获取穿梭表格对象

        :Args:
         - idname: 穿梭表格的定位条件，字典类型：{'元素查找方式':'元素查找语句', '辅助属性':'辅助属性值'}
         - father: 穿梭表格的祖先级元素，可以给穿梭表格查找限定范围，这样就不需要在整个页面中查找

        :Returns:
            返回穿梭表格对象

        :Example:
         - 例1: trans_table = FrontEnd(driver).transfer_table(father=msg)
         - 例2: trans_table = FrontEnd(driver).transfer_table({By.ID:'id'})
        '''
        if father is None:
            father = self.driver
        if idname:
            table = TransferTable(idname, father, self.timeout)
        else:
            table = TransferTable({'css selector': 'div.h-transfer-table'}, father, self.timeout)
        return table

    def calendar(self, idname=None, father=None):
        '''
        获取日期框对象

        :Args:
         - idname: 日历框的定位条件，字典类型：{'元素查找方式':'元素查找语句', '辅助属性':'辅助属性值'}
         - father: 日历框的祖先级元素，可以给日历框查找限定范围，这样就不需要在整个页面中查找

        :Returns:
            hui的日期框对象

        :Usage:
         - calendar = FrontEnd(driver).calendar(father=form)
         - calendar = FrontEnd(driver).calendar({By.ID:'id'})
        '''
        if father is None:
            father = self.driver
        if idname:
            calendar = Calendar(idname, father, self.timeout)
        else:
            calendar = Calendar(
                {'css selector': 'input.h-input'}, father, self.timeout)
        return calendar

    def button(self, text=None, idname=None, father=None, index=1):
        '''
        获取按钮对象

        :Args:
         - text: 按钮文本
         - idname: 按钮的定位条件，字典类型：{'元素查找方式':'元素查找语句', '辅助属性':'辅助属性值'}
         - father: button的祖先级元素，可以给按钮查找限定范围，不需要在整个页面中查找

        :Notes:
            三个参数必须要有一个

        :Example:
         - 例1: button = FrontEnd(driver).button(text='提交',father=msg_box)
         - 例2: button = FrontEnd(driver).button(idname={By.ID:'id'})
        '''
        if not text and not idname and not father:
            raise AttributeError('缺少参数，该方法至少需要一个参数！')
        if father is None:
            father = self.driver
        if idname:
            button = Button(idname, father, self.timeout, index=index)
        else:
            button = Button({'css selector': 'button.h-btn', 'text': text}, father, self.timeout)
        return button

    def select_table(self, text=None, idname=None, father=None, index=1):
        '''
        左侧菜单栏对象

        :Args:
         - idname: 菜单栏的定位条件，字典类型：{'元素查找方式':'元素查找语句', '辅助属性':'辅助属性值'}

        :Raises:
            SeleniumError: 菜单操作失败

        :Return:
            返回Menu对象
        '''
        if not text and not idname and not father:
            raise AttributeError('缺少参数，该方法至少需要一个参数！')
        if father is None:
            father = self.driver
        if idname:
            select_table = SelectTable(idname, self.driver, self.timeout)
        else:
            select_table = SelectTable({'css selector': 'div.h-selectTable'}, father, self.timeout)
        return select_table